# -*- coding: utf-8 -*-

import json
import logging
import re
from typing import Dict, Any, List, Optional, Set, Tuple
from datetime import datetime

# 确保日志配置正确
if not logging.getLogger().handlers:
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 使用一个固定的logger名称，确保全局一致性
logger = logging.getLogger("fact_extractor")
logger.setLevel(logging.INFO)  # 显式设置级别

# 确保logger有handler
if not logger.handlers:
    # 添加一个stream handler，直接输出到控制台
    handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    handler.setLevel(logging.INFO)
    logger.addHandler(handler)
    logger.propagate = True  # 允许传播到父logger


class FactExtractor:
    """
    医生提问解析与患者信息提取补全模块（基于可追踪 PatientState）
    
    功能：根据医生提问，从结构化的 PatientState 中提取或补全相关信息，生成回答所需的结构化语义结果
    特性：可解释、一致性、层次化抽取
    """
    
    def __init__(self, config=None, model_handler=None):
        """
        初始化事实提取器
        
        Args:
            config: 配置对象
            model_handler: 模型处理器，用于LLM调用
        """
        self.config = config
        self.model_handler = model_handler
        self.conflict_threshold = getattr(config, 'CONFLICT_DETECTION_THRESHOLD', 0.8) if config else 0.8
        self.confidence_threshold = getattr(config, 'CONFIDENCE_THRESHOLD', 0.6) if config else 0.6
        
        # 初始化规则匹配器的模式
        self.query_patterns = self._init_query_patterns()
        
        # 冲突检测规则
        self.conflict_rules = self._init_conflict_rules()
    
    def extract_and_validate_facts(self, doctor_utterance: str, patient_state: Dict[str, Any]) -> Dict[str, Any]:
        """
        提取和校验事实（模块二主入口）
        
        Args:
            doctor_utterance: 医生提问
            patient_state: 患者状态
            
        Returns:
            结构化语义结果
        """
        try:
            logger.info(f"开始处理医生提问: {doctor_utterance}")
            
            # Step 1: 意图识别与字段定位
            intent_info = self._recognize_intent_and_target_fields(doctor_utterance)
            intent = intent_info.get("intent", "未知")
            target_fields = intent_info.get("target_fields", [])
            logger.info(f"识别意图: {intent}, 目标字段: {target_fields}")
            
            # Step 2: 字段值检索
            evidence = self._retrieve_field_values(target_fields, patient_state)
            logger.info(f"字段检索完成，找到 {len([v for v in evidence.values() if v['value'] is not None])} 个非空字段\nevidence:{evidence}")
            # Step 3: 信息补全（LLM补充）
            need_completion = any(v["value"] is None or ("confidence" in v and v["confidence"] < self.confidence_threshold) 
                                for v in evidence.values())
            completion = {}
            consistency_check = "无冲突"
            
            if need_completion:
                completion = self._generate_completion(doctor_utterance, patient_state, evidence)
                logger.info(f"生成补充信息，共 {len(completion)} 个字段\n补充：{completion}")
                
                # Step 4: 一致性校验
                consistency_check = self._check_consistency(completion, patient_state)
                logger.info(f"一致性检查结果: {consistency_check}")
            
            # 构建最终输出
            result = {
                "intent": intent,
                "target_fields": target_fields,
                "evidence": evidence,
                "need_completion": need_completion,
            }
            
            # 根据是否需要补全添加相应字段
            if need_completion:
                result["completion"] = completion
                result["consistency_check"] = consistency_check
            else:
                # 不需要补全时，直接使用evidence中的值作为final_answer_content
                result["final_answer_content"] = {}
                for field_path, field_info in evidence.items():
                    if field_info["value"] is not None:
                        result["final_answer_content"][field_path] = field_info["value"]
            
            logger.info("信息提取与补全完成")
            return result
            
        except Exception as e:
            logger.error(f"提取和校验事实失败: {str(e)}")
            return self._get_empty_result(doctor_utterance, patient_state)
    
    def _recognize_intent_and_target_fields(self, doctor_utterance: str) -> Dict[str, Any]:
        """
        意图识别与字段定位
        
        Args:
            doctor_utterance: 医生提问
            
        Returns:
            包含意图和目标字段的字典
        """
        # 规则优先匹配
        for pattern_info in self.query_patterns:
            if re.search(pattern_info["pattern"], doctor_utterance, re.IGNORECASE):
                logger.debug(f"规则匹配成功: {pattern_info['pattern']}")
                return {
                    "intent": pattern_info["intent"],
                    "target_fields": pattern_info["target_fields"]
                }
        
        # 规则未匹配，使用LLM提取补充
        logger.debug("规则匹配失败，使用LLM提取")
        return self._llm_extract_intent_and_fields(doctor_utterance)
    
    def _retrieve_field_values(self, target_fields: List[str], patient_state: Dict[str, Any]) -> Dict[str, Dict[str, Any]]:
        """
        字段值检索
        
        Args:
            target_fields: 目标字段路径列表
            patient_state: 患者状态
            
        Returns:
            证据字典，键为字段路径，值为字段信息
        """
        evidence = {}
        patient_state_data = patient_state.get("PatientState", {})
        
        for field_path in target_fields:
            # 查找字段值
            value_info = self._find_field_by_path(field_path, patient_state_data)
            
            if value_info:
                # 找到了字段
                evidence[field_path] = value_info
            else:
                # 未找到字段，设置为缺失
                evidence[field_path] = {
                    "value": None,
                    "source": "缺失",
                    "confidence": 0.0
                }
        
        return evidence
    
    def _generate_completion(self, doctor_utterance: str, patient_state: Dict[str, Any], 
                           evidence: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
        """
        信息补全（LLM补充）
        
        Args:
            doctor_utterance: 医生提问
            patient_state: 患者状态
            evidence: 已检索的证据
            
        Returns:
            补全的字段信息
        """
        # 确定需要补全的字段
        fields_to_complete = [field_path for field_path, info in evidence.items() 
                            if info["value"] is None or ("confidence" in info and info["confidence"] < self.confidence_threshold)]
        
        if not fields_to_complete:
            return {}
        
        # 使用LLM生成补全内容
        completion_result = self._llm_generate_completion(doctor_utterance, patient_state, fields_to_complete)
        
        # 标准化补全结果格式
        standardized_completion = {}
        for field_path, field_info in completion_result.items():
            if isinstance(field_info, dict):
                standardized_completion[field_path] = {
                    "value": field_info.get("value"),
                    "source": "LLM补充",
                    "confidence": field_info.get("confidence", 0.6)
                }
            elif field_info is not None:
                # 如果返回的是直接值，转换为标准格式
                standardized_completion[field_path] = {
                    "value": field_info,
                    "source": "LLM补充",
                    "confidence": 0.6
                }
        
        return standardized_completion
    
    def _check_consistency(self, completion: Dict[str, Dict[str, Any]], patient_state: Dict[str, Any]) -> str:
        """
        一致性校验
        
        Args:
            completion: 补全的字段信息
            patient_state: 患者状态
            
        Returns:
            一致性检查结果
        """
        patient_state_data = patient_state.get("PatientState", {})
        conflicts_detected = False
        
        for field_path, field_info in completion.items():
            if field_info["value"] is None:
                continue
            
            # 检查冲突
            if self._check_field_conflict(field_path, field_info, patient_state_data):
                conflicts_detected = True
                # 解决冲突
                resolved_value = self._resolve_field_conflict(field_path, field_info, patient_state_data)
                if resolved_value:
                    completion[field_path] = resolved_value
        
        return "已调整" if conflicts_detected else "无冲突"
    
    def _find_field_by_path(self, field_path: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        按层级路径查找字段
        
        Args:
            field_path: 字段路径，如 "现病史.进展"
            data: 要查找的数据字典
            
        Returns:
            字段信息或None
        """
        try:
            parts = field_path.split(".")
            current = data
            
            for part in parts:
                if isinstance(current, dict) and part in current:
                    current = current[part]
                else:
                    return None
            
            # 确保返回的是标准格式
            if isinstance(current, dict) and "value" in current:
                return current.copy()
            
            return None
            
        except Exception as e:
            logger.warning(f"查找字段失败: {field_path}, 错误: {str(e)}")
            return None
    
    def _check_field_conflict(self, field_path: str, field_info: Dict[str, Any], 
                            patient_state_data: Dict[str, Any]) -> bool:
        """
        检查字段是否与现有状态冲突
        
        Args:
            field_path: 字段路径
            field_info: 字段信息
            patient_state_data: 患者状态数据
            
        Returns:
            是否存在冲突
        """
        try:
            value = field_info.get("value")
            if value is None:
                return False
            
            # 查找现有字段值
            existing_field = self._find_field_by_path(field_path, patient_state_data)
            if not existing_field or existing_field["value"] is None:
                return False
            
            existing_value = existing_field["value"]
            
            # 检查语义冲突
            if self._are_values_conflicting(str(value), str(existing_value)):
                logger.warning(f"检测到字段冲突: {field_path} - 新值: {value}, 现有值: {existing_value}")
                return True
            
            return False
            
        except Exception as e:
            logger.warning(f"检查字段冲突失败: {field_path}, 错误: {str(e)}")
            return False
    
    def _resolve_field_conflict(self, field_path: str, field_info: Dict[str, Any], 
                              patient_state_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        解决字段冲突
        
        Args:
            field_path: 字段路径
            field_info: 字段信息
            patient_state_data: 患者状态数据
            
        Returns:
            解决后的字段信息
        """
        # 获取现有字段值
        existing_field = self._find_field_by_path(field_path, patient_state_data)
        if not existing_field:
            return field_info
        
        existing_value = existing_field["value"]
        existing_confidence = existing_field.get("confidence", 0.0)
        new_confidence = field_info.get("confidence", 0.0)
        
        resolved = field_info.copy()
        
        # 策略1：以置信度高者为准
        if new_confidence > existing_confidence:
            # 新值置信度更高，但仍标记为可能冲突
            resolved["confidence"] = min(new_confidence, 0.8)
            resolved["source"] = f"LLM补充（冲突-高置信度优先）"
            logger.info(f"冲突解决：{field_path} - 采用新值")
        else:
            # 现有值置信度更高，保留现有值
            resolved["value"] = existing_value
            resolved["source"] = existing_field["source"]
            resolved["confidence"] = existing_confidence
            logger.info(f"冲突解决：{field_path} - 保留现有值")
        
        return resolved
    
    def _are_values_conflicting(self, value1: str, value2: str) -> bool:
        """
        判断两个值是否语义冲突
        
        Args:
            value1: 第一个值
            value2: 第二个值
            
        Returns:
            是否冲突
        """
        # 空值不冲突
        if not value1 or not value2:
            return False
        
        # 完全相同不冲突
        if value1 == value2:
            return False
        
        # 检查反义词对
        conflict_pairs = [
            ("无", "有"), ("是", "否"), ("加重", "减轻"), ("缓解", "加重"),
            ("疼痛", "无疼痛"), ("发热", "无发热"), ("咳嗽", "无咳嗽"),
            ("急性", "慢性"), ("短期", "长期"), ("剧烈", "轻微")
        ]
        
        # 标准化字符串用于比较
        value1_lower = value1.lower()
        value2_lower = value2.lower()
        
        # 检查直接冲突
        for pos, neg in conflict_pairs:
            if (pos in value1_lower and neg in value2_lower) or \
               (neg in value1_lower and pos in value2_lower):
                return True
        
        # 检查模糊矛盾（如"有点疼"和"不疼"）
        neg_words = ["无", "否", "不", "没有"]
        pos_indicators = ["有点", "有", "轻微", "稍微", "疼", "痛"]
        
        # 特殊情况处理：直接判断"有点疼"和"不疼"这种明显矛盾
        if "有点疼" in value1_lower and "不疼" in value2_lower:
            return True
        if "有点疼" in value2_lower and "不疼" in value1_lower:
            return True
        
        # 如果一个含有否定词，另一个含有肯定指标，可能冲突
        has_neg1 = any(neg in value1_lower for neg in neg_words)
        has_neg2 = any(neg in value2_lower for neg in neg_words)
        has_pos1 = any(pos in value1_lower for pos in pos_indicators)
        has_pos2 = any(pos in value2_lower for pos in pos_indicators)
        
        # 更直接的冲突判断：如果一个是否定，另一个是肯定，且有共同的症状词
        common_symptoms = ["疼", "痛", "发热", "咳嗽", "恶心", "呕吐", "胀", "闷"]
        
        # 检查是否有共同的症状词
        has_common_symptom = False
        for symptom in common_symptoms:
            if symptom in value1_lower and symptom in value2_lower:
                has_common_symptom = True
                break
        
        # 否定+肯定+共同症状 = 冲突
        if (has_neg1 and has_pos2 or has_neg2 and has_pos1) and has_common_symptom:
            return True
        
        return False
    
    def _init_query_patterns(self) -> List[Dict[str, Any]]:
        """
        初始化查询模式
        
        Returns:
            查询模式列表
        """
        return [
            # 过敏相关模式优先，避免被药物相关模式覆盖
            {
                "pattern": "(过敏|过敏史|对什么过敏|有过敏吗)",
                "intent": "询问过敏史",
                "target_fields": ["药物过敏史"]
            },
            {
                "pattern": "(有没有加重|更严重|加重了吗|变严重了吗|厉害了吗)",
                "intent": "询问病情变化",
                "target_fields": ["现病史.进展"]
            },
            {
                "pattern": "(哪里不舒服|哪里疼|什么部位|位置)",
                "intent": "询问症状部位",
                "target_fields": ["主观表述.疼痛部位"]
            },
            {
                "pattern": "(怎么疼|什么样的疼|疼痛性质|疼法)",
                "intent": "询问疼痛性质",
                "target_fields": ["主观表述.疼痛性质"]
            },
            {
                "pattern": "(什么时候开始|持续多久|多久了|多长时间|开始时间)",
                "intent": "询问起病时间",
                "target_fields": ["现病史.起病时间"]
            },
            {
                "pattern": "(还有其他|伴随|一起|另外|除了.*还有)",
                "intent": "询问伴随症状",
                "target_fields": ["现病史.伴随症状"]
            },
            {
                "pattern": "(原因|为什么|怎么回事|怎么引起)",
                "intent": "询问诱因",
                "target_fields": ["现病史.诱因"]
            },
            {
                "pattern": "(吃了什么药|用了什么药|服用什么药物)",
                "intent": "询问用药情况",
                "target_fields": ["现病史.用药情况"]
            },
            {
                "pattern": "(效果|好点了吗|有效果吗|改善)",
                "intent": "询问治疗效果",
                "target_fields": ["现病史.治疗效果"]
            },
            {
                "pattern": "(既往史|以前|曾经|之前|有什么病)",
                "intent": "询问既往病史",
                "target_fields": ["既往史"]
            }
        ]
    
    def _init_conflict_rules(self) -> Dict[str, List[Tuple[str, str]]]:
        """
        初始化冲突检测规则
        
        Returns:
            冲突规则字典
        """
        return {
            "症状": [
                ("疼痛", "无疼痛"),
                ("发热", "无发热"),
                ("咳嗽", "无咳嗽"),
                ("恶心", "无恶心"),
                ("呕吐", "无呕吐")
            ],
            "程度": [
                ("剧烈", "轻微"),
                ("严重", "轻微"),
                ("加重", "减轻")
            ],
            "时间": [
                ("急性", "慢性"),
                ("短期", "长期")
            ]
        }
    
    def _llm_extract_intent_and_fields(self, doctor_utterance: str) -> Dict[str, Any]:
        """
        使用LLM提取意图和字段
        
        Args:
            doctor_utterance: 医生提问
            
        Returns:
            包含意图和目标字段的字典
        """
        try:
            # 清理医生提问文本，去除多余空白字符
            clean_utterance = ' '.join(doctor_utterance.strip().split())
            
            # 构建prompt内容，确保格式正确
            prompt_content = f"""
任务：分析医生提问的意图和需要查询的患者信息字段。
要求：
1. 识别医生提问的语义意图
2. 确定需要查询的患者信息字段路径（支持多级路径，如现病史.进展）
3. 输出格式必须是纯JSON，不包含其他文本，包含intent和target_fields两个字段
4. 如果无法识别意图或没有明确需要查询的字段，请返回空列表

医生提问：{clean_utterance}

可能的字段路径包括：
- 现病史.起病时间
- 现病史.症状
- 现病史.诱因
- 现病史.伴随症状
- 现病史.进展
- 现病史.用药情况
- 现病史.治疗效果
- 主观表述.疼痛部位
- 主观表述.疼痛性质
- 主观表述.情绪状态
- 既往史
- 药物过敏史
- 流行病学史

请只输出JSON格式，例如：
{{"intent": "询问现病史", "target_fields": ["现病史.症状", "现病史.起病时间"]}}
            """
            
            # 使用统一的messages格式，与模块三、四保持一致
            model_input = [
                {"role": "system", "content": "你是一位专业的医疗助手，负责分析医生提问并提取相关信息。请严格按照要求格式输出，只返回纯JSON字符串，不包含任何其他文本。"},
                {"role": "user", "content": prompt_content}
            ]
            
            # 如果有模型处理器，使用它调用LLM，采用generate_with_messages方法
            if self.model_handler:
                response = self.model_handler.generate_with_messages(model_input)
                logger.debug(f"LLM原始响应: {response}")
                
                # 验证和清理响应
                if not response or not response.strip():
                    logger.warning("LLM返回空响应，使用默认值")
                    return {
                        "intent": "未知",
                        "target_fields": []
                    }
                
                # 尝试提取JSON部分（移除可能的前缀和后缀文本）
                clean_response = response.strip()
                # 查找JSON开始和结束位置
                start_idx = clean_response.find('{')
                end_idx = clean_response.rfind('}')
                
                if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
                    clean_response = clean_response[start_idx:end_idx+1]
                    logger.debug(f"提取后的JSON响应: {clean_response}")
                
                # 尝试解析JSON，添加更详细的错误处理
                try:
                    result = json.loads(clean_response)
                    # 验证必要字段存在
                    if isinstance(result, dict):
                        return {
                            "intent": result.get("intent", "未知"),
                            "target_fields": result.get("target_fields", [])
                        }
                    else:
                        logger.error("LLM响应不是有效的字典格式")
                        return {
                            "intent": "未知",
                            "target_fields": []
                        }
                except json.JSONDecodeError as je:
                    logger.error(f"LLM响应JSON解析失败: {str(je)}，响应内容: {clean_response}")
                    # 尝试使用正则表达式提取字段信息作为备用方案
                    import re
                    potential_fields = re.findall(r'现病史\.[\w]+|主观表述\.[\w]+|既往史|药物过敏史|流行病学史', clean_utterance)
                    return {
                        "intent": "未知",
                        "target_fields": list(set(potential_fields))  # 去重
                    }
            else:
                # 模拟LLM响应
                response = json.dumps({
                    "intent": "询问其他信息",
                    "target_fields": ["现病史"]
                })
                result = json.loads(response)
                return {
                    "intent": result.get("intent", "未知"),
                    "target_fields": result.get("target_fields", [])
                }
            
        except Exception as e:
            logger.error(f"LLM提取意图和字段失败: {str(e)}")
            # 返回默认值
            return {
                "intent": "未知",
                "target_fields": []
            }
    
    def _llm_generate_completion(self, doctor_utterance: str, patient_state: Dict[str, Any], 
                               fields_to_complete: List[str]) -> Dict[str, Any]:
        """
        使用LLM生成补全内容
        
        Args:
            doctor_utterance: 医生提问
            patient_state: 患者状态
            fields_to_complete: 需要补全的字段列表
            
        Returns:
            补全的字段信息
        """
        try:
            # 清理医生提问文本
            clean_utterance = ' '.join(doctor_utterance.strip().split())
            
            # 构建patient_state的简化表示
            patient_info = json.dumps(patient_state, ensure_ascii=False, indent=2)
            
            # 构建prompt内容，加强格式要求
            prompt_content = f"""
任务：根据患者现有病历信息，为医生提问补全合理的患者描述。
要求：
- 与已有信息一致；
- 合理自然，不引入冲突；
- 用中性、简短表达；
- 只补全缺失的信息，不要臆测。

医生提问：{clean_utterance}

```
{patient_info}
```

需要补全的字段：
{fields_to_complete}

输出格式要求：
1. 严格只输出JSON格式，不包含任何其他文本、说明或注释
2. JSON的键是字段名，值是包含value和confidence的字典
3. confidence是0-1之间的浮点数，表示补全信息的可信度
4. 只包含要求补全的字段，不要添加其他字段

示例输出：
{{"症状": {{"value": "头痛", "confidence": 0.8}}, "起病时间": {{"value": "3天前", "confidence": 0.9}}}}
            """
            
            # 使用统一的messages格式，加强系统提示
            model_input = [
                {"role": "system", "content": "你是一位专业的医疗助手，负责根据患者信息和医生提问补全缺失的患者描述。请严格按照要求格式输出，只返回纯JSON字符串，不包含任何其他文本、解释或注释。"},
                {"role": "user", "content": prompt_content}
            ]
            logger.info(f"prompt_content:{prompt_content}")
            # 如果有模型处理器，使用它调用LLM
            if self.model_handler:
                response = self.model_handler.generate_with_messages(model_input)
                logger.info(f"response:{response}")
                
                # 验证和清理响应
                if not response or not response.strip():
                    logger.warning("LLM返回空响应，使用默认值")
                    return {}
                
                # 尝试提取JSON部分
                clean_response = response.strip()
                # 查找JSON开始和结束位置
                start_idx = clean_response.find('{')
                end_idx = clean_response.rfind('}')
                
                if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
                    clean_response = clean_response[start_idx:end_idx+1]
                    logger.debug(f"提取后的JSON响应: {clean_response}")
                
                # 尝试解析JSON，添加更详细的错误处理
                try:
                    result = json.loads(clean_response)
                    # 验证结果格式
                    if isinstance(result, dict):
                        # 过滤只保留需要补全的字段
                        filtered_result = {}
                        for field in fields_to_complete:
                            if field in result and isinstance(result[field], dict):
                                # 确保value和confidence字段存在
                                value = result[field].get("value", "")
                                confidence = result[field].get("confidence", 0.0)
                                # 验证confidence是有效数字
                                if isinstance(confidence, (int, float)) and 0 <= confidence <= 1:
                                    filtered_result[field] = {"value": value, "confidence": confidence}
                                else:
                                    filtered_result[field] = {"value": value, "confidence": 0.0}
                        return filtered_result
                    else:
                        logger.error("LLM响应不是有效的字典格式")
                        return {}
                except json.JSONDecodeError as je:
                    logger.error(f"LLM响应JSON解析失败: {str(je)}，响应内容: {clean_response}")
                    return {}
            else:
                # 模拟LLM响应
                mock_response = {}
                for field in fields_to_complete:
                    mock_response[field] = {"value": "需要补充", "confidence": 0.6}
                return mock_response
            
        except Exception as e:
            logger.error(f"LLM生成补全内容失败: {str(e)}")
            # 返回空的补全结果
            return {}
    
    def _get_empty_result(self, doctor_utterance: str, patient_state: Dict[str, Any]) -> Dict[str, Any]:
        """
        获取空的结果
        
        Args:
            doctor_utterance: 医生提问
            patient_state: 患者状态
            
        Returns:
            空结果
        """
        return {
            "intent": "未知",
            "target_fields": [],
            "evidence": {},
            "need_completion": False,
            "final_answer_content": {},
            "_error": "处理失败"
        }
